/*
 * Copyright 2008 Lucas Tan.
 * This copyright notice must always remain intact whenever you use or distribute 
 * the source code contained in this file.
 * Any usage of any code in this file, either in derived work or
 * through the use of external linkage,
 * must always be credited with the author's name.
 * 
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, 
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 
 * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
 * DEALINGS IN THE SOFTWARE.
 * 
 * */
using System;
using System.Collections.Generic;
using System.Text;
using System.Xml;
using System.Security.Cryptography;
using PassKeeper.Cryptography;

namespace PassKeeper.Data
{
	public sealed class SymmetricCipher : Element, IDisposable
	{
		private const string SymmetricCipherElementName = "SymmetricCipher";
		private const string KeySizeAttributeName = "KeySize";
		private const string BlockSizeAttributeName = "BlockSize";
		private const string FeedbackSizeAttributeName = "FeedbackSize";
		private const string IVAttributeName = "IV";
		private const string PaddingAttributeName = "Padding";
		private const string ModeAttributeName = "Mode";
		private const string NameAttributeName = "Name";


		private SymmetricAlgorithm _symAlgo;
		private SymmetricCipherType _cipherType;

		internal SymmetricCipher(Database owner, SymmetricCipherType cipherType)
			: base(owner)
		{
			_cipherType = cipherType;
			_symAlgo = Create(cipherType);
		}

		private static SymmetricAlgorithm Create(SymmetricCipherType cipherType)
		{
			switch (cipherType)
			{
			case SymmetricCipherType.AES:
				return new AESManaged();
			}

			throw new ArgumentException("Unknown symmetric cipher type.", "cipherType");
		}

		public override void Deserialize(XmlElement elem)
		{
			if (elem == null)
			{
				throw new ArgumentNullException("elem");
			}

			SymmetricAlgorithm symAlgo = null;
			SymmetricCipherType cipherType;
			bool ok = false;

			try
			{
				string name = elem.GetAttribute(NameAttributeName);

				if (string.IsNullOrEmpty(name))
				{
					throw new MissingAttributeException(elem.Name, NameAttributeName);
				}

				
				try
				{
					cipherType = (SymmetricCipherType) Enum.Parse(typeof(SymmetricCipherType), name, false);
				}
				catch (Exception e)
				{
					throw new AttributeParseException(elem.Name, NameAttributeName, e);
				}

				symAlgo = Create(cipherType);
				symAlgo.KeySize = ParserUtil.ParsePositiveInteger(elem, KeySizeAttributeName);

				/*Set feedbacksize after settings blocksize since the validity
				 of the former is determined by the latter.*/
				symAlgo.BlockSize = ParserUtil.ParsePositiveInteger(elem, BlockSizeAttributeName);
				symAlgo.FeedbackSize = ParserUtil.ParsePositiveInteger(elem, FeedbackSizeAttributeName);

				string ivStr = elem.GetAttribute(IVAttributeName);
				string modeStr = elem.GetAttribute(ModeAttributeName);
				string paddingStr = elem.GetAttribute(PaddingAttributeName);


				if (string.IsNullOrEmpty(ivStr))
				{
					throw new MissingAttributeException(elem.Name, IVAttributeName);
				}
				if (string.IsNullOrEmpty(modeStr))
				{
					throw new MissingAttributeException(elem.Name, ModeAttributeName);
				}
				if (string.IsNullOrEmpty(paddingStr))
				{
					throw new MissingAttributeException(elem.Name, PaddingAttributeName);

				}

				try
				{
					symAlgo.IV = Convert.FromBase64String(ivStr);
				}
				catch (Exception e)
				{
					throw new AttributeParseException(elem.Name, IVAttributeName, e);
				}

				try
				{
					symAlgo.Mode = (CipherMode) Enum.Parse(typeof(CipherMode), modeStr, false);
				}
				catch (Exception e)
				{
					throw new AttributeParseException(elem.Name, ModeAttributeName, "Unrecognized mode.", e);
				}

				try
				{
					symAlgo.Padding = (PaddingMode) Enum.Parse(typeof(PaddingMode), paddingStr, false);
				}
				catch (Exception e)
				{
					throw new AttributeParseException(elem.Name, PaddingAttributeName, "Unrecognized padding mode.", e);
				}

				ok = true;
			}
			finally
			{
				if (!ok)
				{
					if (symAlgo != null)
					{
						((IDisposable) symAlgo).Dispose();
					}
				}
			}

			((IDisposable) _symAlgo).Dispose();
			_symAlgo = symAlgo;
			_cipherType = cipherType;
		}

		public override void Serialize(XmlElement elem)
		{
			if (elem == null)
			{
				throw new ArgumentNullException("elem");
			}
		
			elem.SetAttribute(NameAttributeName, Enum.GetName(typeof(SymmetricCipherType),_cipherType));
			elem.SetAttribute(KeySizeAttributeName, _symAlgo.KeySize.ToString());
			elem.SetAttribute(BlockSizeAttributeName, _symAlgo.BlockSize.ToString());
			elem.SetAttribute(FeedbackSizeAttributeName, _symAlgo.FeedbackSize.ToString());
			elem.SetAttribute(ModeAttributeName, Enum.GetName(typeof(CipherMode), _symAlgo.Mode));
			elem.SetAttribute(PaddingAttributeName, Enum.GetName(typeof(PaddingMode), _symAlgo.Padding));
			elem.SetAttribute(IVAttributeName,
				Convert.ToBase64String(_symAlgo.IV, Base64FormattingOptions.None));
		}

		protected internal override Element Clone(Database ownerDatabase)
		{
			SymmetricCipher symCipher = new SymmetricCipher(this.OwnerDatabase, _cipherType);

			symCipher._symAlgo.KeySize = _symAlgo.KeySize;
			symCipher._symAlgo.BlockSize = _symAlgo.BlockSize;
			symCipher._symAlgo.FeedbackSize = _symAlgo.FeedbackSize;
			symCipher._symAlgo.Padding = _symAlgo.Padding;
			symCipher._symAlgo.Mode = _symAlgo.Mode;
			symCipher._symAlgo.IV = _symAlgo.IV;

			return symCipher;
		}

		public ICryptoTransform CreateEncryptor(byte[] keyBytes, byte[] ivBytes)
		{
			return _symAlgo.CreateEncryptor(keyBytes, ivBytes);
		}

		public ICryptoTransform CreateDecryptor(byte[] keyBytes, byte[] ivBytes)
		{
			return _symAlgo.CreateDecryptor(keyBytes, ivBytes);
		}

		public void Dispose( )
		{
			((IDisposable) _symAlgo).Dispose();
			
		}

		public override string TagName
		{
			get
			{
				return SymmetricCipherElementName;
			}
		}

		public int KeySize
		{
			get
			{
				return _symAlgo.KeySize;
			}
			set
			{
				_symAlgo.KeySize = value;
			}
		}

		public int BlockSize
		{
			get
			{
				return _symAlgo.BlockSize;
			}
			set
			{
				_symAlgo.BlockSize = value;
			}
		}


		public int FeedbackSize
		{
			get
			{
				return _symAlgo.FeedbackSize;
			}
			set
			{
				_symAlgo.FeedbackSize = value;
			}
		}

		public PaddingMode Padding
		{
			get
			{
				return _symAlgo.Padding;
			}
			set
			{
				_symAlgo.Padding = value;
			}
		}

		public CipherMode Mode
		{
			get
			{
				return _symAlgo.Mode;
			}
			set
			{
				_symAlgo.Mode = value;
			}
		}

		public byte[] IV
		{
			get
			{
				return _symAlgo.IV;
			}
			set
			{
				_symAlgo.IV = value;
			}
		}
	}
}
